Skip to content

vulkan: use density gate for MUL_MAT_VEC_ID path - #27332

Open
theycallmeloki wants to merge 1 commit into
ggml-org:masterfrom
theycallmeloki:vulkan-moe-density-gate
Open

vulkan: use density gate for MUL_MAT_VEC_ID path#27332
theycallmeloki wants to merge 1 commit into
ggml-org:masterfrom
theycallmeloki:vulkan-moe-density-gate

Conversation

@theycallmeloki

Copy link
Copy Markdown

Replace the fixed 8-token cutoff with the #25356 density gate (n_tokens * experts_per_token <= 2 * n_experts, capped at 64 tokens). Avoids the batch-9 decode regression on AMD RADV; validated on gfx1151, RDNA3 and gfx1013 (BC-250): +36% at B=9, +27% at B=16, +21% at B=64, neutral at B<=8.

Assisted-by: DeepSeek V4 Flash

Overview

Replace the hardcoded batch <= 8 threshold for the Vulkan GGML_OP_MUL_MAT_ID MMV path with the routed-density heuristic proposed in #25356.

For MoE decode, the MMV kernel performs better for small routed workloads, while the tiled kernel wins at larger workloads. The fixed cutoff causes a sharp kernel-selection regression when going from 8 to 9 concurrent sequences on AMD RADV.

The new gate keeps MMV selected while:

n_tokens * experts_per_token <= 2 * n_experts

and n_tokens <= 64.

The existing F32/F16/quantized type check is unchanged, and large-batch prefill remains on the tiled path.

Additional information

On a BC-250 (gfx1013), the new gate produced:

  • B=9: +36%
  • B=16: +27%
  • B=64: +21%
  • B<=8: neutral
  • PP512: neutral (303-304 t/s)

The same density heuristic has previously been validated on Strix Halo (gfx1151) and RDNA3 in the discussion of #25356.

Forcing the tiled path everywhere was also tested on gfx1013 and performed substantially worse (-44% at tg64, about -50% at B=1), supporting kernel selection rather than removal of the MMV path.

The change is limited to ggml_vk_use_mul_mat_vec_id(): 9 insertions, 1 deletion, with no new configuration or environment knobs.

Correctness: test-backend-ops -b Vulkan0 -o MUL_MAT_ID passes 872/872
on the patched build (gfx1013, RADV).

Related work

Repro

Reproduction (llama-batched-bench, Qwen3.5-35B-A3B, 4-node RPC):
stock: B=8 52.6, B=9 35.9 t/s (cliff)
patched: B=8 51.5, B=9 48.2 t/s (+36%)

Requirements

@theycallmeloki
theycallmeloki requested a review from a team as a code owner August 18, 2026 16:08
@jeffbolznv

Copy link
Copy Markdown
Contributor

I ran this on my 5090 using the command line from the issue, and while it is marginally better for B==9, it is slower for larger values. So this would need more tuning:

llama-batched-bench -m c:\models\Qwen_Qwen3-30B-A3B-Q4_K_M.gguf -c 32768 -ngl 999 -fa 1 -ctk q4_0 -ctv q4_0 --no-mmap -npp 512 -ntg 128 -npl 1,2,4,8,9,12,16,24,32
0.00.241.886 W DEPRECATED: --mmap and --no-mmap are deprecated. use --load-mode mmap instead
0.00.382.854 W load: control-looking token: 128247 '</s>' was not control-type; this is probably a bug in the model. its type will be overridden

llama_batched_bench: n_kv_max = 32768, n_batch = 2048, n_ubatch = 512, flash_attn = 1, is_pp_shared = 0, is_tg_separate = 0, n_gpu_layers = 999, n_threads = 24, n_threads_batch = 24

|    PP |     TG |    B |   N_KV |   T_PP s | S_PP t/s |   T_TG s | S_TG t/s |      T s |    S t/s |
|-------|--------|------|--------|----------|----------|----------|----------|----------|----------|
|   512 |    128 |    1 |    640 |    0.120 |  4278.11 |    0.501 |   255.47 |    0.621 |  1031.07 |
|   512 |    128 |    2 |   1280 |    0.103 |  9979.63 |    0.678 |   377.32 |    0.781 |  1638.75 |
|   512 |    128 |    4 |   2560 |    0.192 | 10674.45 |    0.956 |   535.34 |    1.148 |  2229.46 |
|   512 |    128 |    8 |   5120 |    0.372 | 11005.55 |    1.577 |   649.54 |    1.949 |  2627.41 |
|   512 |    128 |    9 |   5760 |    0.423 | 10884.69 |    1.970 |   584.69 |    2.394 |  2406.40 |
|   512 |    128 |   12 |   7680 |    0.563 | 10920.14 |    2.099 |   731.69 |    2.662 |  2885.19 |
|   512 |    128 |   16 |  10240 |    0.748 | 10948.33 |    2.289 |   894.65 |    3.037 |  3371.29 |
|   512 |    128 |   24 |  15360 |    1.120 | 10969.40 |    2.600 |  1181.59 |    3.720 |  4128.93 |
|   512 |    128 |   32 |  20480 |    1.490 | 10993.86 |    2.865 |  1429.46 |    4.356 |  4701.88 |

after:

|    PP |     TG |    B |   N_KV |   T_PP s | S_PP t/s |   T_TG s | S_TG t/s |      T s |    S t/s |
|-------|--------|------|--------|----------|----------|----------|----------|----------|----------|
|   512 |    128 |    1 |    640 |    0.120 |  4275.93 |    0.497 |   257.80 |    0.616 |  1038.53 |
|   512 |    128 |    2 |   1280 |    0.100 | 10191.49 |    0.677 |   378.41 |    0.777 |  1647.37 |
|   512 |    128 |    4 |   2560 |    0.191 | 10749.70 |    0.958 |   534.26 |    1.149 |  2228.30 |
|   512 |    128 |    8 |   5120 |    0.372 | 10999.78 |    1.599 |   640.38 |    1.971 |  2597.12 |
|   512 |    128 |    9 |   5760 |    0.424 | 10863.77 |    1.863 |   618.27 |    2.287 |  2518.11 |
|   512 |    128 |   12 |   7680 |    0.569 | 10803.59 |    2.132 |   720.53 |    2.700 |  2843.95 |
|   512 |    128 |   16 |  10240 |    0.739 | 11085.64 |    2.525 |   811.11 |    3.264 |  3137.35 |
|   512 |    128 |   24 |  15360 |    1.107 | 11102.95 |    3.357 |   914.98 |    4.464 |  3440.72 |
|   512 |    128 |   32 |  20480 |    1.472 | 11128.76 |    4.229 |   968.59 |    5.701 |  3592.32 |

@theycallmeloki
theycallmeloki force-pushed the vulkan-moe-density-gate branch from 84b3b8f to aba6a48 Compare August 18, 2026 19:30
@theycallmeloki

Copy link
Copy Markdown
Author

I have added vendor_id == VK_VENDOR_ID_AMD so this doesn't regress nvidia cards

For now, not quite sure how to proceed in this path, because I don't have a 5090 to tune blackwell but I tried the gated fix in my latest revision on my ampere cards (3090 + a4000) and don't see any degradation, I am fairly certain in between AMD cards there could also be the same blackwell/ampere batch processing discrepencies where different cards might require different tuning

Screenshot From 2026-08-19 01-09-54

@jeffbolznv

Copy link
Copy Markdown
Contributor

I doubt it's related to architectural differences. I think the coopmat2 path does better with small batches than coopmat1 due to the enable_smaller_matrices optimizations. NV is similarly slow for B>8 with coopmat1:

|    PP |     TG |    B |   N_KV |   T_PP s | S_PP t/s |   T_TG s | S_TG t/s |      T s |    S t/s |
|-------|--------|------|--------|----------|----------|----------|----------|----------|----------|
|   512 |    128 |    1 |    640 |    2.291 |   223.51 |    0.510 |   250.85 |    2.801 |   228.49 |
|   512 |    128 |    2 |   1280 |    0.148 |  6907.86 |    0.711 |   360.16 |    0.859 |  1490.05 |
|   512 |    128 |    4 |   2560 |    0.280 |  7303.15 |    0.974 |   525.44 |    1.255 |  2040.07 |
|   512 |    128 |    8 |   5120 |    0.552 |  7424.07 |    1.600 |   640.07 |    2.152 |  2379.70 |
|   512 |    128 |    9 |   5760 |    0.623 |  7390.87 |    3.860 |   298.45 |    4.483 |  1284.75 |
|   512 |    128 |   12 |   7680 |    0.821 |  7482.75 |    4.043 |   379.92 |    4.864 |  1578.92 |
|   512 |    128 |   16 |  10240 |    1.096 |  7476.10 |    4.289 |   477.47 |    5.385 |  1901.58 |
|   512 |    128 |   24 |  15360 |    1.637 |  7504.52 |    4.700 |   653.57 |    6.338 |  2423.57 |
|   512 |    128 |   32 |  20480 |    2.184 |  7501.76 |    4.475 |   915.26 |    6.659 |  3075.43 |

@github-actions github-actions Bot added Vulkan Issues specific to the Vulkan backend ggml changes relating to the ggml tensor library for machine learning labels Aug 18, 2026
@ghost

This comment was marked as low quality.

Replace the fixed 8-token cutoff with the ggml-org#25356 density gate
(n_tokens * experts_per_token <= 2 * n_experts, cap 64 tokens) on
devices without coopmat2. The tiled path is slow at small batch on
coopmat1/no-coopmat hardware (decode cliff at 9+ concurrent
sequences); validated on gfx1013, gfx1100, gfx1151 and NV coopmat1.
coopmat2 devices (Blackwell) keep the fixed cutoff - their tiled path
handles small batches well.

Assisted-by: DeepSeek V4 Flash
@theycallmeloki
theycallmeloki force-pushed the vulkan-moe-density-gate branch from aba6a48 to 4dfa152 Compare August 22, 2026 13:53
@theycallmeloki

Copy link
Copy Markdown
Author

@jeffbolznv thank you, you were right, it wasn't hardware differences, it ended up being based on coopmatmul class based, so I've dropped the gate on AMD class and gone specifically to !ctx->device->coopmat2 so blackwell can also benefit from this, I believe it should not regress.
@sswtodo thank you, the coopmat1 data was helpful, Nvidia coopmat1 gets the fix and coopmat2 has the fixed cutoff.

Open question is whether the gate should be the long term fix or whether coopmat1's tiled path should get the enable_smaller_matrices-style optimization instead because that would likely fix root cause for everyone.

@jeffbolznv

Copy link
Copy Markdown
Contributor

I did some experimentation today. It's not just the enable_smaller_matrices optimization, there is also some tile size and tile size selection tuning needed (and these are the bulk of the perf gain). Here are some measurements, all on RTX 5090 with coopmat2 disabled:

llama-batched-bench -m c:\models\Qwen_Qwen3-30B-A3B-Q4_K_M.gguf -c 32768 -ngl 999 -fa 1 -ctk q4_0 -ctv q4_0 --no-mmap -npp 512 -ntg 128 -npl 1,2,4,8,9,12,16,24,32

master:

|    PP |     TG |    B |   N_KV |   T_PP s | S_PP t/s |   T_TG s | S_TG t/s |      T s |    S t/s |
|-------|--------|------|--------|----------|----------|----------|----------|----------|----------|
|   512 |    128 |    1 |    640 |    0.084 |  6090.24 |    0.485 |   263.86 |    0.569 |  1124.44 |
|   512 |    128 |    2 |   1280 |    0.150 |  6821.35 |    0.629 |   407.27 |    0.779 |  1643.79 |
|   512 |    128 |    4 |   2560 |    0.284 |  7208.02 |    0.909 |   563.42 |    1.193 |  2146.09 |
|   512 |    128 |    8 |   5120 |    0.566 |  7233.35 |    1.621 |   631.62 |    2.188 |  2340.57 |
|   512 |    128 |    9 |   5760 |    0.647 |  7122.41 |    4.687 |   245.77 |    5.334 |  1079.79 |
|   512 |    128 |   12 |   7680 |    1.446 |  4249.89 |    4.652 |   330.19 |    6.098 |  1259.53 |
|   512 |    128 |   16 |  10240 |    4.486 |  1826.26 |    4.876 |   420.02 |    9.362 |  1093.83 |
|   512 |    128 |   24 |  15360 |    1.747 |  7032.84 |    5.381 |   570.88 |    7.128 |  2154.75 |
|   512 |    128 |   32 |  20480 |    3.343 |  4900.98 |    5.140 |   796.83 |    8.483 |  2414.14 |

this PR:

|    PP |     TG |    B |   N_KV |   T_PP s | S_PP t/s |   T_TG s | S_TG t/s |      T s |    S t/s |
|-------|--------|------|--------|----------|----------|----------|----------|----------|----------|
|   512 |    128 |    1 |    640 |    0.085 |  5992.93 |    0.491 |   260.49 |    0.577 |  1109.53 |
|   512 |    128 |    2 |   1280 |    0.147 |  6977.71 |    0.630 |   406.58 |    0.776 |  1648.63 |
|   512 |    128 |    4 |   2560 |    0.288 |  7110.81 |    0.903 |   566.88 |    1.191 |  2149.08 |
|   512 |    128 |    8 |   5120 |    0.563 |  7271.05 |    1.628 |   628.83 |    2.192 |  2336.04 |
|   512 |    128 |    9 |   5760 |    0.652 |  7067.88 |    3.338 |   345.16 |    3.990 |  1443.76 |
|   512 |    128 |   12 |   7680 |    0.849 |  7233.43 |    3.251 |   472.47 |    4.100 |  1872.98 |
|   512 |    128 |   16 |  10240 |    4.989 |  1641.89 |    3.696 |   554.13 |    8.685 |  1179.01 |
|   512 |    128 |   24 |  15360 |    2.605 |  4716.52 |    4.507 |   681.66 |    7.112 |  2159.74 |
|   512 |    128 |   32 |  20480 |    3.239 |  5058.00 |    4.933 |   830.35 |    8.172 |  2506.10 |

tile size tuning:

|    PP |     TG |    B |   N_KV |   T_PP s | S_PP t/s |   T_TG s | S_TG t/s |      T s |    S t/s |
|-------|--------|------|--------|----------|----------|----------|----------|----------|----------|
|   512 |    128 |    1 |    640 |    0.453 |  1129.62 |    0.487 |   262.60 |    0.941 |   680.35 |
|   512 |    128 |    2 |   1280 |    0.139 |  7363.78 |    0.633 |   404.61 |    0.772 |  1658.52 |
|   512 |    128 |    4 |   2560 |    0.263 |  7775.63 |    0.915 |   559.83 |    1.178 |  2173.28 |
|   512 |    128 |    8 |   5120 |    0.523 |  7834.39 |    1.621 |   631.81 |    2.144 |  2388.55 |
|   512 |    128 |    9 |   5760 |    0.595 |  7739.22 |    3.084 |   373.51 |    3.680 |  1565.35 |
|   512 |    128 |   12 |   7680 |    0.788 |  7794.45 |    3.200 |   479.95 |    3.989 |  1925.48 |
|   512 |    128 |   16 |  10240 |    1.453 |  5639.45 |    3.607 |   567.73 |    5.060 |  2023.72 |
|   512 |    128 |   24 |  15360 |    1.564 |  7854.63 |    3.782 |   812.21 |    5.347 |  2872.79 |
|   512 |    128 |   32 |  20480 |    2.451 |  6683.77 |    3.976 |  1030.17 |    6.427 |  3186.37 |

tile size tuning + enable_smaller_matrices:

|    PP |     TG |    B |   N_KV |   T_PP s | S_PP t/s |   T_TG s | S_TG t/s |      T s |    S t/s |
|-------|--------|------|--------|----------|----------|----------|----------|----------|----------|
|   512 |    128 |    1 |    640 |    0.073 |  7053.80 |    0.490 |   261.35 |    0.562 |  1138.09 |
|   512 |    128 |    2 |   1280 |    0.127 |  8051.07 |    0.635 |   402.84 |    0.763 |  1678.30 |
|   512 |    128 |    4 |   2560 |    0.243 |  8423.03 |    0.908 |   564.10 |    1.151 |  2224.56 |
|   512 |    128 |    8 |   5120 |    0.475 |  8621.43 |    1.617 |   633.17 |    2.092 |  2447.01 |
|   512 |    128 |    9 |   5760 |    0.545 |  8452.22 |    2.809 |   410.18 |    3.354 |  1717.50 |
|   512 |    128 |   12 |   7680 |    0.709 |  8668.67 |    2.959 |   519.06 |    3.668 |  2093.82 |
|   512 |    128 |   16 |  10240 |    0.972 |  8428.76 |    3.149 |   650.44 |    4.121 |  2485.10 |
|   512 |    128 |   24 |  15360 |    1.827 |  6727.08 |    3.504 |   876.60 |    5.331 |  2881.22 |
|   512 |    128 |   32 |  20480 |    1.894 |  8649.53 |    3.770 |  1086.49 |    5.664 |  3615.73 |

I don't object to the current PR. If somebody has the time and AMD/Intel hardware available, it would be good to tune the tiling parameters and then maybe revisit this. It should also benefit larger prompt processing workloads. It doesn't make sense to tune it based on NVIDIA hardware performance.

@Stoney49th

Copy link
Copy Markdown

I couldnt measure any improvements on 2x R9700, Q3.8-27B and Q3.6 35B with single / parallel sessions up to np=3 with MTP=1 (because otherwise it trashes parallel session performance).

@frizikk

frizikk commented Aug 24, 2026

Copy link
Copy Markdown

Independent Linux Strix Halo / RADV test of current head 4dfa152da618f530c9fcf355abec0265ebd92b40 against its parent ed1c3a20f5e3f0892ac7135d43542815bd7c4aba.

System: Ryzen AI MAX+ 395 / Radeon 8060S (RADV STRIX_HALO), Linux 7.2.0, Mesa RADV 26.2.1, Vulkan 1.4.357. llama.cpp reports UMA, subgroup 64, KHR_coopmat.

Model: bartowski/Qwen_Qwen3-30B-A3B-GGUF/Qwen_Qwen3-30B-A3B-Q4_K_M.gguf, 18,632,184,480 bytes, SHA-256 a015794bfb1d69cb03dbb86b185fb2b9b339f757df5f8f9dd9ebdab8f6ed5d32.

Build: Release + GGML_VULKAN=ON. Three clean, interleaved parent/head process runs:

llama-batched-bench -m Qwen_Qwen3-30B-A3B-Q4_K_M.gguf \
  -c 32768 -ngl 999 -fa on -ctk q4_0 -ctv q4_0 -lm none \
  -npp 512 -ntg 128 -npl 1,2,4,8,9,12,16,24,32

Mean ± sample SD, aggregate decode throughput:

B Parent S_TG t/s PR S_TG t/s Delta
1 84.54 ± 0.52 84.27 ± 0.97 -0.32%
2 124.28 ± 0.11 123.26 ± 0.87 -0.82%
4 175.66 ± 0.58 176.12 ± 0.97 +0.26%
8 219.42 ± 0.47 218.71 ± 1.23 -0.32%
9 130.61 ± 0.14 199.34 ± 0.89 +52.62%
12 155.75 ± 0.12 224.53 ± 0.13 +44.16%
16 188.59 ± 0.14 252.63 ± 0.67 +33.96%
24 239.47 ± 0.26 282.04 ± 0.17 +17.78%
32 293.99 ± 0.52 310.21 ± 1.23 +5.52%

Prompt processing was neutral at every B (all deltas between -0.16% and +0.36%).

Targeted correctness at the exact PR head:

test-backend-ops test -b Vulkan0 -o MUL_MAT_ID
872/872 tests passed
Backend Vulkan0: OK

So on Linux/RADV Strix Halo this head removes most of the B=8→9 MoE decode cliff without a measurable B≤8 or prompt-processing regression.

@theycallmeloki

Copy link
Copy Markdown
Author

@jeffbolznv Thank you for the coopmat1 experiments, the B=9 collapse on the 5090 with coopmat2 disabled was instrumental to validate the gate fixes, your PR-vs-master table (B=9 +40%, B=32 +4%) confirms the gate helps on the coopmat1 path too, not just for AMD devices.

Agreed that tile-size tuning + enable_smaller_matrices is the better root-cause fix for coopmat1 since your numbers beat the gate at every B>=9 and also lift prefill. That portion of the work ideally requires AMD/Intel hardware to tune and likely doesnt benefit from just Nvidia hardware performance tuning.

One context note from my side: My hardware (BC-250, gfx1013) has no matrix cores at all, no coopmat1, no coopmat2, so I'm unable to tile tune from here. The gate is the only applicable fix for my class of hardware. My data (B=9 +36%, B=16 +27%, B=64 +21%, B<=8 neutral) stands as the no-matrix-core case for the gate.

Separately, I have Ampere cards (3090 + A4000, no coopmat2) that could validate the gate on another no-coopmat2 class via Vulkan. The tiling work itself is best done in a separate PR with AMD/Intel hardware, as you said: NVIDIA-derived tile parameters won't port.

If the tiling work later supersedes the gate, the single !coopmat2 condition keeps the revert to a one-liner.

@Stoney49th Thanks for testing on RDNA4. Worth noting: the gate is intentionally inactive on coopmat2-class devices (RDNA4 included) since they keep the fixed 8-token cutoff, since the tiled path handles small batches well there. So no improvement is the expected result on that hardware, particularly thank you for confirming and thanks for confirming it doesn't regress either. If you notice B=9 decode drop on RDNA4, that would be a seperate issue worth reporting

@frizikk Thanks for the test, this is the cleanest validation: interleaved parent/head runs on the exact hardware from #25356, tight SDs, B=9 +52.6% and B<=8/prefill within noise. The 872/872 MUL_MAT_ID test on the head is appreciated too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ggml changes relating to the ggml tensor library for machine learning Vulkan Issues specific to the Vulkan backend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants